package framework.elements;

import java.util.List;

import org.testng.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;

import framework.BaseEntity;
import framework.Browser;
import framework.Logger;

/**
 * Abstract class containing base element (a control). This class contains
 * common methods
 */
public abstract class BaseElement extends BaseEntity {

    /** Element name. */
    private String name;
    /** Element locator. */
    protected By locator;
    /** Reference to WebDriver's element. */
    protected RemoteWebElement element;

    /**
     * Get wedriver's element instance.
     * 
     * @return Webdriver element reference
     * */
    public final RemoteWebElement getElement() {
        assertIsPresent();
        return element;
    }

    // TODO: Create a separate class for this
    /**
     * Is element collapsed.
     * 
     * @return true if collapsed
     */
    public final boolean isCollapsed() {
        assertIsPresent();
        String elementClass = element.getAttribute("class");
        return elementClass.toLowerCase().contains("collapse");
    }

    /**
     * Is element enabled.
     * 
     * @return true if enabled
     */
    public boolean isEnabled() {
        assertIsPresent();
        String elementClass = element.getAttribute("class");
        return element.isEnabled()
                && (!elementClass.toLowerCase().contains("disabled"));
    }

    // TODO: Create a separate class for this
    /**
     * Is element checked.
     * 
     * @return true if checked
     */
    public final boolean isChecked() {
        assertIsPresent();
        String elementClass = element.getAttribute("class");
        return !elementClass.toLowerCase().contains("unchecked");
    }
    /**
     * Default constructor.
     */
    protected BaseElement() {
    }

    /**
     * Main constructor.
     * 
     * @param locator
     *            Element locator
     * @param name
     *            Element name
     */
    protected BaseElement(final By locator, final String name) {
        this.locator = locator;
        this.name = name;
    }

    /**
     * Get element locator.
     * 
     * @return Locator
     */
    public final By getLocator() {
        return locator;
    }

    /**
     * Get element name.
     * 
     * @return Element name
     */
    public final String getName() {
        return name;
    }

    /**
     * Implementation of abstract logging method from BaseEntity.
     * 
     * @param message
     *            Message to be logged
     * @return Formatted message with element type and name
     */
    protected final String formatLogMsg(final String message) {
        return String.format("%1$s '%2$s' %3$s %4$s", getElementType(), name,
                Logger.LOG_DELIMITER, message);
    }

    /**
     * Get element type (used in logging).
     * 
     * @return element type
     */
    protected abstract String getElementType();

    /**
     * Click on the element.
     */
    public final void click() {
        final int numberOfAttempts = 5;
        final int waitTimeAfterFailure = 1000;

        debug("Click");
        assertIsPresent();
        info("Clicking");
        element.getLocationOnScreenOnceScrolledIntoView();
        for (int i = 0; i < numberOfAttempts; i++) {
            try {
                browser.getDriver().getMouse()
                        .mouseMove(element.getCoordinates());
                element.click();
                break;
            } catch (Throwable e) {
                warn("click failed, retrying");
                try {
                    Thread.sleep(waitTimeAfterFailure);
                } catch (InterruptedException e1) {
                    debug("Interrupted sleep");
                }
            }
        }
        try {
            Thread.sleep(Integer.parseInt(Browser.props
                    .getProperty("speedValue")));
        } catch (InterruptedException e) {
            debug("Interrupted sleep");
        }
    }
    
    public final void waitForElementToBeLoaded(){
      //Wait until element has no "loader" class
        WebDriverWait wait = new WebDriverWait(browser.getDriver(), 
                Long.parseLong(Browser.getInstance().getTimeoutForCondition()));
        try {
            wait.until((ExpectedCondition<Boolean>) 
                    new ExpectedCondition<Boolean>() {
            public Boolean apply(WebDriver d) {
                String elementClass =
                    d.findElement(locator).getAttribute("class");
                return (!elementClass.contains("loader"))
                    && (!elementClass.contains("mbLoading"));
            }
            });
        } catch (Exception e) {
            Assert.assertTrue(false, "Element loader didn't disappear");
        }
    }

    /**
     * Click on the element and wait for page to load.
     */
    public final void clickAndWait() {
        debug("clickAndWait");
        // Chrome hack - focus on element
        assertIsPresent();
        click();
        browser.waitForPageToLoad();
    }

    /**
     * TODO: JavaDoc: Fill me in.
     */
    public final void clickAndWaitNoNavigation() {
        debug("clickAndWaitNoNavigation");
        clickNoNavigation();
        browser.waitForPageToLoad();
    }

    /**
     * TODO: JavaDoc: Fill me in.
     */
    public final void clickNoNavigation() {
        debug("clickNoNavigation");
        assertIsPresent();
        info("Clicking");
        element.click();
    }

    /**
     * Click on the element and wait for new window to appear.
     */
    public final void clickAndWaitForNewWindow() {
        debug("clickAndWaitForNewWindow");
        int count = browser.getDriver().getWindowHandles().size();
        click();
        browser.waitForNewWindow(count);
        browser.getDriver()
                .switchTo()
                .window((String) browser.getDriver().getWindowHandles()
                        .toArray()[count]);
    }

    /**
     * Get element text (inner text).
     * 
     * @return element inner text
     */
    public final String getText() {
        debug("getText");
        assertIsPresent();
        return element.getText();
    }

    /**
     * Check that element is present with custom timeout
     * The test will fail if element is absent
     */
    public final void assertIsPresent(long timeout) {
        if (!isPresent(timeout)) {
            fatal("is absent");
        }
    }
    
    /**
     * Check that element is present. The test will fail if element is absent
     */
    public final void assertIsPresent() {
        if (!isPresent(browser.getTimeoutForConditionLong())) {
            fatal("is absent");
        }
    }

    /**
     * Check that element is absent. The test will fail if element is present
     */
    public final void assertIsAbsent() {
        if (isPresent()) {
            fatal("is present");
        }
    }

    /**
     * Wait for element to appear and fail if it is absent.
     */
    public final void waitAndAssertIsPresent() {
        debug("waitAndAssertIsPresent");
        assertIsPresent();
    }
    
    /**
     * Wait for element with timeout to appear and fail if it is absent.
     */
    public final void waitAndAssertIsPresent(long timeout) {
        debug("waitAndAssertIsPresent");
        assertIsPresent(timeout);
    }

    /**
     * Check that element is present on the page.
     * 
     * @param timeout
     *            Expected timeout to wait for element
     * @return true if element is present
     */
    public final boolean isPresent(final long timeout) {
        WebDriverWait wait = new WebDriverWait(browser.getDriver(), timeout);

        try {
            wait.until((ExpectedCondition<Boolean>) 
                    new ExpectedCondition<Boolean>() {
                public Boolean apply(final WebDriver driver) {
                    try {
                        List<WebElement> list = driver.findElements(locator);
                        for (WebElement el : list) {
                            if (el instanceof RemoteWebElement
                                    && el.isDisplayed()) {
                                element = (RemoteWebElement) el;
                                if (element.isDisplayed()) {
                                    return element.isDisplayed();
                                }
                            }
                        }
                        element = (RemoteWebElement) driver
                                .findElement(locator);
                    } catch (Exception e) {
                        return false;
                    }
                    return element.isDisplayed();
                }
            });
        } catch (Exception e) {
            debug(e.toString());
        }
        try {
            return element.isDisplayed();
        } catch (Exception e) {
            debug(e.toString());
        }
        return false;
    }

    /**
     * Short check for element to be present.
     * 
     * @return true if element is present
     */
    public final boolean isPresent() {
        return isPresent(1);
    }

    /**
     * Wait for element to disappear, default timeout is used
     */
    public final void waitForElementDissapear() {
        waitForElementDissapear(browser.getTimeoutForConditionLong());
    }

    public void waitForElementDissapear(long timeout) {
        WebDriverWait wait = new WebDriverWait(browser.getDriver(), timeout);

        try {
            wait.until((ExpectedCondition<Boolean>) 
                    new ExpectedCondition<Boolean>() {
                public Boolean apply(final WebDriver driver) {
                    try {
                        List<WebElement> list = driver.findElements(locator);
                        for (WebElement el : list) {
                            if (el instanceof RemoteWebElement
                             && el.isDisplayed()) {
                                Thread.sleep(100);
                            }
                        }
                        element = (RemoteWebElement) driver
                                .findElement(locator);
                    } catch (NoSuchElementException e) {
                        return true;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        return true;
                    }
                    return !element.isDisplayed();
                }
            });
        } catch (Exception e) {
            debug(e.toString());
        }
    }

    /**
     * Wait while Element dissapear
     */
    /*
     * public void waitForElementDissapear() { Future<?> future = null;
     * ExecutorService executor = null; boolean result;
     * 
     * try { executor = Executors.newFixedThreadPool(1); future =
     * executor.submit(new Runnable() {
     * 
     * @Override public void run() { element = null; List<WebElement> list =
     * browser.getDriver().findElements(locator);
     */
    // ***********************************************************************
    // This is for
    // isPresent!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // ***********************************************************************
    /*
     * for (WebElement el : list) { if (el != null && el instanceof
     * RemoteWebElement && el.isDisplayed()) { element=(RemoteWebElement) el;
     * break; } }
     */
    /*
     * while (true) { element = null; if (list.size() == 0) { break; } for
     * (WebElement el : list) { if (el != null && el instanceof RemoteWebElement
     * && el.isDisplayed()) { element = (RemoteWebElement) el; } } if (element
     * == null) { break; } list = browser.getDriver().findElements(locator); }
     * 
     * } }); future.get(browser.getTimeoutForConditionLong(), TimeUnit.SECONDS);
     * result = element == null; } catch (Exception e) { result = false; }
     * finally { future.cancel(true); executor.shutdown(); }
     * Assert.assertTrue("Element doesn't dissapear", result); }
     */
}
